import {BaseSession} from '../base'
import {BoolValue, FloatValue, fromAny, StringValue, toAny} from '../../proto'
import {Message} from "protobufjs";


export class MediaRecorderSession extends BaseSession {
    private recorder: MediaRecorder
    private blobOptions: FilePropertyBag
    private recordedListener: () => void
    private filename: string
    private data: Blob

    constructor(render: HTMLElement) {
        super("MediaRecorder", render);
    }

    async onCreate(sessionId: number, futureId: number, ...argv) {
        super.onCreate(sessionId, futureId, ...argv);
        const format = argv.length > 1 ? fromAny(argv[0], StringValue) : "aac"
        this.blobOptions = this.getBlobOptions(format)
        this.filename = 'record_' + new Date().getTime() + '.' + format

    }

    async onReceive(name: String, sessionId: number, futureId: number, ...argv) {
        switch (name) {
            case "start":
                return await this.start()
            case "stop":
                return await this.stop(sessionId, futureId)
            case 'getFilename':
                return this.getFilename(sessionId, futureId)
            case 'pause':
                return this.pause()
            case 'resume':
                return this.resume()
            case 'upload':
                return this.upload(sessionId, futureId, ...argv)
        }
        super.onReceive(name, sessionId, futureId, ...argv);
    }


    onDestroy() {
        if (this.recorder != null)
            this.recorder.stop()
        this.recorder = null
        super.onDestroy();
    }

    private getBlobOptions(format): BlobPropertyBag {
        switch (format) {
            case "aac":
                return {type: "audio/aac; codecs=aac"}
            case "ogg":
                return {type: "audio/opus; codecs=opus"}
            case "3gp":
                return {type: "audio/3gp; codecs=3gp"}
        }
    }

    private async start() {
        const stream = await navigator.mediaDevices.getUserMedia({audio: true})
        this.recorder = new MediaRecorder(stream);
        this.recorder.start()
    }

    private async stop(sessionId: number, futureId: number) {
        if (this.recorder == null) {
            this.sendAction("onStop", sessionId, futureId)
            return
        }

        this.recorder.ondataavailable = (e) => {
            this.data = e.data
            this.recordedListener()
        }
        this.recorder.stop()
        this.sendAction("onStop", sessionId, futureId, toAny(true, BoolValue))
        this.recorder = null
    }
    private getFilename(sessionId: number, futureId: number) {
        this.sendAction('onGetFilename', sessionId, futureId, toAny(this.filename, StringValue))
    }
    private pause() {
        this.recorder.pause()
    }
    private resume() {
        this.recorder.resume()
    }
    private upload(sessionId: number, futureId: number, ...argv) {
        if (!this.data)
            throw new Error('You have not recorded an audio yet.')
        const url = fromAny(argv[0], StringValue)
        const xhr = new XMLHttpRequest()
        xhr.open('POST', url, true)
        // 文件上传中持续触发
        xhr.upload.onprogress = (e) => {
            // loaded文件已上传大小，total文件总大小
            const progress = toAny(e.loaded / e.total * 100, FloatValue)
            argv[1] && this.sendCallback(argv[1], progress)
        }
        // 文件上传结束触发
        xhr.onload = () => this.onUpload(sessionId, futureId, toAny(true, BoolValue))
        xhr.onerror = () => this.onError(new Error(xhr.statusText), sessionId, futureId)
        const file = new File([this.data], this.filename, this.blobOptions)
        const formData = new FormData()
        formData.append('file', file)
        xhr.send(formData)
    }
    private onUpload(sessionId: number, futureId: number, result: Message) {
        this.sendAction('onUpload', sessionId, futureId, result)
    }
    public listenRecorded(onchange: (url: string) => void) {
        this.recordedListener = () => {
            const reader = new FileReader()
            reader.onload = () => onchange(reader.result as string)
            reader.readAsDataURL(this.data)
        }
    }
}
